// #include "iwl_connector.h"

#ifndef __IWL_CONNECTOR_H__
#define __IWL_CONNECTOR_H__

#include <linux/connector.h>

#ifdef CN_NETLINK_USERS
#undef CN_NETLINK_USERS
#define CN_NETLINK_USERS		11	/* Highest index + 1 */
#endif

#define CN_IDX_IWLAGN   (CN_NETLINK_USERS + 0xf)
#define CN_VAL_IWLAGN   0x1

#endif /* __IWL_CONNECTOR_H__ */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <linux/netlink.h>
#include <pthread.h>
#include <stdbool.h>
#include <errno.h>
#include <fcntl.h>

#define MAX_PAYLOAD     8192
#define SLOW_MSG_CNT    1
#define SERVER_PORT     8090
#define DEBUG           2

// Socket file descriptor
int sock_csi_fd = -1;
int sock_server_fd = -1;
int sock_client_fd = -1;               

// File pointer for the output file
FILE* out = NULL;

// Function prototypes
// FILE* open_file(char* filename, char* spec);    // Open a file and check for errors
void exit_program_err(int code, char* func);    // Exit the program with an error
void setup_tcp_serv();                          // Setup the TCP connection (server for remitting the CSI data)
void setup_netlink();                           // sets up a Netlink socket and subscribes to a specific Netlink group to receive events.


// Receive data from the Netlink socket and store it in the data buffer
// Then send the data to the client
void non_block_send()
{
    // Create a new block of memory for the data buffer
    char temp_buffer[MAX_PAYLOAD];
    memset(temp_buffer, 0, sizeof(temp_buffer));

    // Local variables
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);

    // uint8_t result[16];

    // Counter
    int count = 0;

    // Your logic for thread 0 here.
    while (1)
    {

        // Check if there is a client connected
        if (sock_client_fd == -1)
        {
            // Wait for a connection
            sock_client_fd = accept(sock_server_fd, (struct sockaddr *) &client_addr, &client_len);

            // Check for errors
            if (sock_client_fd < 0) {
                if (errno == EWOULDBLOCK || errno == EAGAIN) { /* Do nothing */ }
            }
        }

        // If the socket is valid, then receive data
        while (sock_client_fd != -1)
        {
            // Receive from socket, and check for errors
            size_t recv_len = recv(sock_csi_fd, temp_buffer, sizeof(temp_buffer), 0);
            if (recv_len == -1) {
                perror("recv from netlink failed");
                close(sock_client_fd);
                sock_client_fd = -1;
                continue;
            }

            // Pull out the message portion
            struct cn_msg *cmsg = NLMSG_DATA(temp_buffer);

            // Print out the brief
            if (count % SLOW_MSG_CNT == 0)
                printf("received %d bytes: id: %d val: %d seq: %d clen: %d\n", cmsg->len, cmsg->id.idx, cmsg->id.val, cmsg->seq, cmsg->len);

            // Print out how many bytes were written in the file
            if (count % 100 == 0)
                printf("received %ld bytes [msgcnt=%u]\n", recv_len, cmsg->seq);
            
            // Increment the counter
            ++count;

            // Send data to client
            unsigned short l = (unsigned short) cmsg->len;
            unsigned short l2 = htons(l);

            // Send the length of the data
            if (write(sock_client_fd, &l2, sizeof(unsigned short)) < 0)
            {
                perror("write");
                close(sock_client_fd);
                sock_client_fd = -1;
                continue;
            }

            if (write(sock_client_fd, cmsg->data, l) < 0)
            {
                perror("write");
                close(sock_client_fd);
                sock_client_fd = -1;
                continue;
            }
        }
    }
}


int main(int argc, char** argv)
{
    // Netlink is a socket protocol used for communication between the kernel and user-space processes in Linux. 
    // The kernel uses Netlink to communicate information about the system to user-space processes, 
    // and user-space processes can use Netlink to configure and query the kernel.
    setup_netlink();

    // Setup a tcp non-blocking server to send the csi data
    setup_tcp_serv();

    // Non-blocking send
    non_block_send();

    // Exit the program
    exit(0);
}

void exit_program_err(int code, char* func)
{
    perror(func);
    exit(code);
}


void setup_netlink() {
    // Local variables for netlink
    struct sockaddr_nl proc_addr, kern_addr;    // addrs for recv, send, bind
    int ret;

    // Setup the socket
    sock_csi_fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
    if (sock_csi_fd == -1)
        exit_program_err(-1, "setup netlink socket failed ");

    // The first block of code initializes two struct sockaddr_nl structures, proc_addr and kern_addr, 
    // with the necessary parameters for Netlink communication.
    memset(&proc_addr, 0, sizeof(struct sockaddr_nl));
    proc_addr.nl_family = AF_NETLINK;
    proc_addr.nl_pid = getpid();            // this process' PID
    proc_addr.nl_groups = CN_IDX_IWLAGN;
    memset(&kern_addr, 0, sizeof(struct sockaddr_nl));
    kern_addr.nl_family = AF_NETLINK;
    kern_addr.nl_pid = 0;                   // kernel
    kern_addr.nl_groups = CN_IDX_IWLAGN;

    // The bind() function is used to bind the Netlink socket to the proc_addr structure.
    if (bind(sock_csi_fd, (struct sockaddr *)&proc_addr, sizeof(struct sockaddr_nl)) == -1)
    {
        exit_program_err(-1, "bind netlink failed");
    } 
    
    // The setsockopt() function is used to subscribe the Netlink socket to a specific Netlink group (CN_IDX_IWLAGN in this case).
    int on = proc_addr.nl_groups;
    ret = setsockopt(sock_csi_fd, 270, NETLINK_ADD_MEMBERSHIP, &on, sizeof(on));
    if (ret) exit_program_err(-1, "setsockopt netlink failed");
}


void setup_tcp_serv()
{
    // Local variables
    struct sockaddr_in server_addr;

    // Create the TCP server-side socket
    sock_server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_server_fd < 0)
    {
        exit_program_err(1, "failed at creating tcp server socket");
    }

    // Set socket options
    int flags = fcntl(sock_server_fd, F_GETFL, 0);
    if (flags == -1) {
        exit_program_err(1, "failed at getting socket flags -1");
    }
    flags |= O_NONBLOCK;
    if (fcntl(sock_server_fd, F_SETFL, flags) == -1) {
        exit_program_err(1, "failed at getting socket flags as NONBLOCK");
    }

    // Bind the socket
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(SERVER_PORT);
    if (bind(sock_server_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
        exit_program_err(1, "failed at binding socket");
    }

    // Listen for connections
    if (listen(sock_server_fd, 5) < 0) {
        exit_program_err(1, "failed at listening on socket");
    }
}